package security

import (
	"crypto/rand"
	"crypto/subtle"
	"errors"
	"time"

	"golang.org/x/crypto/argon2"
	"golang.org/x/crypto/bcrypt"
)

// AuthService provides secure authentication services
type AuthService struct {
	userRepository UserRepository
	passwordPolicy *PasswordPolicy
}

// UserRepository interface for user data access
type UserRepository interface {
	GetUserByUsername(username string) (*SecureUser, error)
	CreateUser(user *SecureUser) error
	UpdateLastLogin(userID int64) error
}

// SecureUser represents a user with security considerations
type SecureUser struct {
	ID           int64     `json:"id"`
	Username     string    `json:"username"`
	PasswordHash string    `json:"-"` // Never expose password hash
	Salt         string    `json:"-"` // Never expose salt
	Role         string    `json:"role"`
	IsActive     bool      `json:"is_active"`
	LastLogin    time.Time `json:"last_login"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

// PasswordPolicy defines password requirements
type PasswordPolicy struct {
	MinLength      int
	RequireUpper   bool
	RequireLower   bool
	RequireDigit   bool
	RequireSpecial bool
}

// AuthRequest represents authentication request
type AuthRequest struct {
	Username string `json:"username" binding:"required,min=3,max=50"`
	Password string `json:"password" binding:"required,min=8,max=128"`
}

// AuthResponse represents authentication response
type AuthResponse struct {
	Token        string    `json:"token"`
	RefreshToken string    `json:"refresh_token"`
	ExpiresAt    time.Time `json:"expires_at"`
	User         *PublicUser `json:"user"`
}

// PublicUser represents user data safe for public exposure
type PublicUser struct {
	ID       int64  `json:"id"`
	Username string `json:"username"`
	Role     string `json:"role"`
}

// NewAuthService creates a new authentication service
func NewAuthService(userRepo UserRepository) *AuthService {
	return &AuthService{
		userRepository: userRepo,
		passwordPolicy: &PasswordPolicy{
			MinLength:      8,
			RequireUpper:   true,
			RequireLower:   true,
			RequireDigit:   true,
			RequireSpecial: true,
		},
	}
}

// Authenticate verifies user credentials securely
func (s *AuthService) Authenticate(req *AuthRequest) (*SecureUser, error) {
	// Rate limiting should be implemented at middleware level
	
	// Get user from repository
	user, err := s.userRepository.GetUserByUsername(req.Username)
	if err != nil {
		// Use constant-time comparison to prevent username enumeration
		// Still perform password hashing to prevent timing attacks
		_, _, _ = s.hashPassword("dummy-password")
		return nil, errors.New("invalid credentials")
	}

	// Check if user is active
	if !user.IsActive {
		return nil, errors.New("account is disabled")
	}

	// Verify password using constant-time comparison
	if !s.verifyPassword(req.Password, user.PasswordHash, user.Salt) {
		return nil, errors.New("invalid credentials")
	}

	// Update last login time
	s.userRepository.UpdateLastLogin(user.ID)

	return user, nil
}

// hashPassword creates a secure password hash with salt
func (s *AuthService) hashPassword(password string) (string, string, error) {
	// Generate random salt
	salt := make([]byte, 32)
	if _, err := rand.Read(salt); err != nil {
		return "", "", err
	}

	// Use Argon2id for password hashing (more secure than bcrypt)
	hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
	
	return string(hash), string(salt), nil
}

// verifyPassword verifies password against hash using constant-time comparison
func (s *AuthService) verifyPassword(password, hash, salt string) bool {
	// Hash the provided password with the stored salt
	providedHash := argon2.IDKey([]byte(password), []byte(salt), 1, 64*1024, 4, 32)
	
	// Use constant-time comparison to prevent timing attacks
	return subtle.ConstantTimeCompare([]byte(hash), providedHash) == 1
}

// hashPasswordBcrypt alternative using bcrypt (simpler but less secure)
func (s *AuthService) hashPasswordBcrypt(password string) (string, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hash), nil
}

// verifyPasswordBcrypt verifies bcrypt password
func (s *AuthService) verifyPasswordBcrypt(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

// ValidatePassword checks password against policy
func (s *AuthService) ValidatePassword(password string) error {
	if len(password) < s.passwordPolicy.MinLength {
		return errors.New("password too short")
	}

	hasUpper := false
	hasLower := false  
	hasDigit := false
	hasSpecial := false

	for _, char := range password {
		switch {
		case 'A' <= char && char <= 'Z':
			hasUpper = true
		case 'a' <= char && char <= 'z':
			hasLower = true
		case '0' <= char && char <= '9':
			hasDigit = true
		case char >= 32 && char <= 126: // Printable ASCII special chars
			if !((char >= '0' && char <= '9') || (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
				hasSpecial = true
			}
		}
	}

	if s.passwordPolicy.RequireUpper && !hasUpper {
		return errors.New("password must contain uppercase letter")
	}
	if s.passwordPolicy.RequireLower && !hasLower {
		return errors.New("password must contain lowercase letter")
	}
	if s.passwordPolicy.RequireDigit && !hasDigit {
		return errors.New("password must contain digit")
	}
	if s.passwordPolicy.RequireSpecial && !hasSpecial {
		return errors.New("password must contain special character")
	}

	return nil
}

// CreateUser creates a new user with secure password hashing
func (s *AuthService) CreateUser(username, password, role string) (*SecureUser, error) {
	// Validate password policy
	if err := s.ValidatePassword(password); err != nil {
		return nil, err
	}

	// Hash password securely
	hash, salt, err := s.hashPassword(password)
	if err != nil {
		return nil, err
	}

	user := &SecureUser{
		Username:     username,
		PasswordHash: hash,
		Salt:         salt,
		Role:         role,
		IsActive:     true,
		CreatedAt:    time.Now(),
		UpdatedAt:    time.Now(),
	}

	if err := s.userRepository.CreateUser(user); err != nil {
		return nil, err
	}

	return user, nil
}